package Graph;

import java.util.ArrayList;
import java.util.HashSet;

import util.Util;

public class ForceGraphDrawer extends GraphDrawer {

   public double k_rep, k_att, spring_length, max_step;

    
    double time_step = 0.03;

    public ForceGraphDrawer(Graph g) {
        super(g);

        k_rep = 100;
        k_att = 100.0;
        max_step = 100;
        spring_length = 30;

    }

    public void iteration() {


        //stub: assign random positions to nodes
        ArrayList<String> nodes = graph.getNodes();

//        for (int i = 0; i < nodes.size(); i++) {
//            int x = (int) (Math.random() * 1000);
//            int y = (int) (Math.random() * 1000);
//
//            setX(i, x);
//            setY(i, y);
//        }

        
        
        // for your implementation do this:

        //init force-vectors to (0,0) for all nodes
        double dsp_x[] = new double[nodes.size()];
        double dsp_y[] = new double[nodes.size()];

        for (int i = 0; i < nodes.size(); i++) {
            dsp_x[i] = 0;
            dsp_y[i] = 0;
        }



        //compute attraction forces and add them to the force vectors
		/* get the edges */
        ArrayList<Integer> eV = new ArrayList<Integer>();
        ArrayList<Integer> eU = new ArrayList<Integer>();

        graph.getEdgesAsIndeces(eV, eU);


        int vIndx, uIndx;
       
        for (int i = 0; i < eV.size(); i++) {
            vIndx = eV.get(i);
            uIndx = eU.get(i);


            double x1 = getX(vIndx);
            double y1 = getY(vIndx);
            double x2 = getX(uIndx);
            double y2 = getY(uIndx);


            double edge_dist = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));


            if (edge_dist > 0) {
                double deltaX = x2 - x1;
                double deltaY = y2 - y1;


                dsp_x[vIndx] = dsp_x[vIndx] + (deltaX / edge_dist) * fa(edge_dist);
                dsp_y[vIndx] = dsp_y[vIndx] + (deltaY / edge_dist) * fa(edge_dist);
                dsp_x[uIndx] = dsp_x[uIndx] - (deltaX / edge_dist) * fa(edge_dist);
                dsp_y[uIndx] = dsp_y[uIndx] - (deltaY / edge_dist) * fa(edge_dist);
            }
        }


        //compute repulsive forces
        for (int i = 0; i < nodes.size() - 1; i++) {

            for (int j = i+1; j < nodes.size(); j++) {
                double x1 = getX(i);
                double y1 = getY(i);
                double x2 = getX(j);
                double y2 = getY(j);
                
                 double edge_dist = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
              
                while (edge_dist < 1) {
                    x2 = x2 + Math.random();
                    y2 = y2 + Math.random();
                    edge_dist = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
                }
                
                double deltaX = x2-x1;
                double deltaY = y2-y1;


                dsp_x[i] = dsp_x[i] + (deltaX / edge_dist) * fr(edge_dist);
                dsp_y[i] = dsp_y[i] + (deltaY / edge_dist) * fr(edge_dist);
                dsp_x[j] = dsp_x[j] - (deltaX / edge_dist) * fr(edge_dist);
                dsp_y[j] = dsp_y[j] - (deltaY / edge_dist) * fr(edge_dist);
            }
            }



            //add forces to positions
            for (int i = 0; i < nodes.size(); i++) {
                double dist = Math.sqrt(dsp_x[i] * dsp_x[i] + dsp_y[i] * dsp_y[i]);

                double newX = (dsp_x[i] / dist) * Math.min(dist, max_step);
                double newY = (dsp_y[i] / dist) * Math.min(dist, max_step);

                setX(i, getX(i) + (int) newX);
                setY(i, getY(i) + (int) newY);

            }
            //cool down system (max_step)     
            max_step = Math.max(3, max_step * 0.90);  //reduce max_step by 10% or take 3

        }  
    

    public double fa(double z) {

        z = z - spring_length;

        return ((z * z) / k_att);
    }

    public double fr(double z) {
        //   z = z + spring_length;

        return (-k_rep / (z * z));
    }
}
